<?php

declare(strict_types=1);

namespace JfbFcnext;

use Hyperf\Utils\Codec\Json;
use Hyperfx\Framework\Logger\Logx;
use Hyperfx\Utils\GenerateId;
use Hyperf\Guzzle\HandlerStackFactory;
use GuzzleHttp\Client;
use Swoole\Http\Request;

class FcClient  {
    protected static $config;

    protected \GuzzleHttp\Client $httpClient;
    public function __construct(array $config)
    {
        self::$config = $config;
        $factory = new HandlerStackFactory();
        $stack = $factory->create([
            'max_connections' => 10
        ]);

        $this->httpClient = make(Client::class, [
            'config' => [
                'handler' => $stack,
            ],
        ]);
    }

    private function getInstanceConfig(string $instance): array
    {
        if (!isset(self::$config['mapping'][$instance])) {
            throw new \Exception(sprintf('fc instance %s not found', $instance));
        }
        return self::$config['mapping'][$instance];
    }

    /**
     * 按配置文件里已经已经配置的函数计算执行函数计算函数服内的command脚本
     * 仅支持异步执行
     *
     * @param string $instance 配置名称
     * @param string $command 命令名称
     * @param array $args 命令参数
     * @param int $deply 延迟执行时间，单位秒
     */
    public function runCommand(string $instance = 'default', string $command = '', array $args = [], int $deply = 0): array
    {
        if (empty($command)) {
            throw new \Exception('command 不能为空');
        }
        $config = $this->getInstanceConfig($instance);
        return $this->asyncInvokeFunction($config['service'], $config['function'], [
            'command' => $command,
            'args' => $args,
        ], $deply);
    }

    /**
     * 自定义服务名与函数名执行函数计算command脚本
     * 仅支持异步执行
     *
     * @param string $serviceName 服务名
     * @param string $functionName 函数名
     * @param string $command 命令名称
     * @param array $args 命令参数
     * @param int $deply 延迟执行时间，单位秒
     */
    public function runCommandWithFunction(string $serviceName, string $functionName, string $command, array $args = [], int $deply = 0): array
    {
        return $this->asyncInvokeFunction($serviceName, $functionName, [
            'command' => $command,
            'args' => $args,
        ], $deply);
    }

    /**
     * 异步执行函数计算，传入的是原始payload
     *
     * @param string $serviceName 服务名
     * @param string $functionName 函数名
     * @param array $payload 传入函数计算的内容
     */
    public function asyncInvokeFunction(string $serviceName, string $functionName, array $payload, int $deply = 0): array
    {
        if ($deply > 3600 || $deply < 0) {
            throw new \Exception(sprintf('AsyncDelay in seconds exceeds range (allowed: (0, 3600], actual: %s)', $deply));
        }
        $ret = retry((self::$config['max_attempts'] ?? 3) - 1, function () use ($serviceName, $functionName, $payload, $deply) {
            return $this->send($serviceName, $functionName, $payload, $deply);
        });
        if (isset($ret['error_code'])) {
            throw new \Exception(sprintf('Fcnext 异步执行失败 %s %s', $ret['error_code'], $ret['error_message']));
        }
        return $ret;
    }

    private function send(string $serviceName, string $functionName, array $body, int $deply = 0) {
        $strBody = Json::encode([
            'data' => [
                'messageBody' => $body
            ],
        ]);
        $byteBody = Utils::toBytes($strBody);
        $signatureAlgorithm = ApiClientUtil::DefaultSignatureAlgorithm;
        $hashedRequestPayload = ApiClientUtil::hexEncode(ApiClientUtil::hash($byteBody, $signatureAlgorithm));
        $action = 'InvokeFunction';
        $traceId = self::getTraceId();
        $headers = [
            'Host' => self::$config['endpoint'],
            'x-acs-version' => '2021-04-06',
            'x-acs-action' => $action,
            'x-acs-date' => ApiClientUtil::getTimestamp(),
            'x-acs-signature-nonce' => Utils::getNonce(),
            'Accept' => 'application/json',
            'X-Fc-Invocation-Type' => 'Async',
            'X-Fc-Trace-Id' => $traceId,
            'x-acs-content-sha256' => $hashedRequestPayload,
        ];
        if ($deply > 0) {
            $headers['X-Fc-Async-Delay'] = $deply;
        }

        $request = new \GuzzleHttp\Psr7\Request(
            method: 'POST',
            uri: sprintf('http://%s/2021-04-06/services/%s/functions/%s/invocations', self::$config['endpoint'], $serviceName, $functionName),
            headers: $headers,
            body: $strBody,
        );

        $authorization = ApiClientUtil::getAuthorization($request, $signatureAlgorithm, $hashedRequestPayload, self::$config['access_key_id'], self::$config['access_key_secret']);
        $request = $request->withAddedHeader('Authorization', $authorization);

        $response = $this->httpClient->send($request, [
            'http_errors' => false,
            'timeout' => self::$config['timeout'] ?? 3,
        ]);

        if ($response->getStatusCode() === 202 || $response->getStatusCode() === 200) {
            return [
                'request_id' => $response->getHeader('x-fc-request-id')[0] ?? $traceId
            ];
        }

        $contents = $response->getBody()->getContents();
        $arrContents = [];
        try {
            $arrContents = Json::decode($contents, true);
        } catch (\Exception $e) {
        }

        // 报错了，这种错误不重试
        if (isset($arrContents['Message'])) {
            Logx::get()->alert('fcnext send failed', $arrContents);
            return [
                'request_id' => $arrContents['RequestID'] ?? $traceId,
                'error_code' => $arrContents['Code'] ?? '',
                'error_message' => $arrContents['Message'] ?? ''
            ];
        }

        Logx::get()->alert('fcnext send failed', [
            'statusCode' => $response->getStatusCode(),
            'ReasonPhras' => $response->getReasonPhrase(),
        ]);

        throw new \Exception(sprintf('Fcnext服务异常 %s', $response->getReasonPhrase()));
    }

    private static function getTraceId(): string
    {
        return sprintf('%s-%s-%s', Logx::getTraceId(), microtime(true) * 10000, GenerateId::randId(16));
    }
}